#!/usr/bin/env bash

source ../../lib/shell/common.sh
readonly essh="ssh -o StrictHostKeyChecking=no"

#*******************************************************************************************
# 测试名: 网卡设备SR-IOV环境检查
#
# 测试步骤: 1、执行lspci | grep "Ethernet controller" 查询网卡pci设备
#          2、检查有几个网卡，以及每个网卡有几个网口，输出每个网卡的每个网口的信息
#          3、检查每个网卡是否支持SR-IOV功能。对于支持的提示检查成功，对于不支持的网卡提示检查失败。
#          4、检查支持SR-IOV功能的网卡是否已经开启了VF，如果开启，则删除VF，重新生成，即重置初始环境
#          5、对于支持SR-IOV功能的网卡，每个网卡选择一个物理网口创建两个VF。失败则提示错误，退出。成功继续
#          6、对5中创建的两个VF配置临时IP
#          7、检查IP是否配置成功，失败提示错误，退出，成功继续。
#          8、在两个VF上进行网络测试。
#          9、检查测试结果
#          10、循环下一张物理网卡进行测试。
#
# 预期结果: 1、命令执行成功
#          2、命令执行成功，服务状态信息获取成功
#          3、网络功能测试正常。
#*******************************************************************************************
# # # # # # # # # # # # # # main # # # # # # # # # # # # # #
write_messages info "==============Network SR-IOV test==================="

server_ip="127.0.0.1"
network_pci_info=""
network_pci_bdf=""
network_pci_count=0
network_pci_num=0
network_avail_ip=""
runtime=270
#   获取hostip
local_host_ip=$(hostname -I)
declare -A Node

# 初始化链表头节点
declare -i head=0
Node[$head]=""
Node["$head.next"]=-1

# 添加节点到链表尾部
appendNode() {
    local nodeValue=$1
    local newNodeIndex=${#Node[@]}
    Node[$newNodeIndex]=$nodeValue
    Node["$newNodeIndex.next"]=-1

    local curr=$head
    while [ ${Node["$curr.next"]} -ne -1 ]; do
    curr=${Node["$curr.next"]}
    done

    Node["$curr.next"]=$newNodeIndex
}

# 遍历链表并打印节点值
traverseList() {
    local curr=$head

    pushd /etc/sysconfig/network-scripts
    while [ $curr -ne -1 ]; do
        local currValue=${Node[$curr]}
        write_messages info "Clear env, remove net dev configure file: $currValue."
        rm -f $currValue
        curr=${Node["$curr.next"]}
    done
    popd
}

function set_server_ip()
{
    [ -n "$SERVER" ] && server_ip=${SERVER%% *} || server_ip="127.0.0.1"
}

set_server_ip

function remote_run_cmd()
{
    $essh "$server_ip" "$1"
}

function do_network_vf_dev_clear()
{
    local phydev_bdf=""
    local phydev_bdf_extend=""

    phydev_bdf=$1
    if [ "${phydev_bdf}" == "" ]; then
        write_messages info "Network phy bdf info is null, cannot clear vf."
        test_fail
    fi
    phydev_bdf_extend=$(ls -l /sys/bus/pci/devices/ | grep "${phydev_bdf}" | awk -F '->' '{ print $1 }' | awk '{ print $NF }')
#   start clear vf
    echo 0 > /sys/bus/pci/devices/"${phydev_bdf_extend}"/sriov_numvfs
    if [ $? -ne 0 ]; then
        write_messages err "Clear VF failed."
        return 1
    fi
    return 0
}

function network_vf_dev_clear()
{
    network_pci_bdf=$(lspci | grep "Ethernet controller" | awk '{ print $1 }')
    echo "${network_pci_bdf}" | while read network_port_bdf
    do
        write_messages info "==============Ready to clear, Check SR_IOV for: "${network_port_bdf}"=================="
        network_port_sriov_check "${network_port_bdf}"
        if [ $? -ne 0 ]; then
            write_messages info "Network port ${network_port_bdf} SR-IOV is not supported, do not need to clear vf for it."
            continue
        fi
        write_messages info "=============Support SR-IOV, clear vf for: "${network_port_bdf}"=================="
        do_network_vf_dev_clear "${network_port_bdf}"
        if [ $? -ne 0 ]; then
            write_messages err "Clear VF devs for Phy dev ${bdf} failed."
            clear_env
            test_fail
        fi
        write_messages info "=============Clear vf successfully for: "${network_port_bdf}"=================="
    done
    return 0
}

function stop_iperf_serve_process()
{
    # 指定要kill的进程名
    process_name="iperf3"

    # 使用pgrep命令查找所有同名进程的PID，并使用kill命令将其kill掉
    pids=$(pgrep $process_name)
    if [[ -n $pids ]]; then
    echo "Killing all processes named $process_name..."
    kill $pids
    else
    echo "No processes named $process_name found."
    fi
    return 0
}
function clear_env()
{
    network_vf_dev_clear
    traverseList
    if [ $? -eq 0 ]; then
        write_messages info "*************Step4: Restore env, clear vf successfully.**************"
    else
        write_messages info "*************Step4: Restore env, clear vf failed.*************"
    fi
    stop_iperf_serve_process
}

# step 1: check network dev
function network_port_sriov_check()
{
    local bdf=$1
    local supportSRIOV=0

    if [ ${bdf} == "" ];then
        write_messages info "Network port info is null."
        clear_env
        test_fail
    fi

    supportSRIOV=$(lspci -vvv -s ${bdf} | grep "SR-IOV" | wc -l)
#   检查物理设备是否支持SR-IOV
    if [ $supportSRIOV -eq 0 ]; then
        write_messages err "Network port ${bdf} does not support SR-IOV, can not test for it."
        return 1
    fi

    return 0
}

function network_port_check()
{
    local haveVF=0

    network_pci_info=$(lspci | grep "Ethernet controller")

    write_messages info "==============Network card info==================="
    write_messages info "${network_pci_info}"

#   检查是否包含VF网卡设备, 如果包含，则退出测试，需要让告知用户清理环境，删除掉已创建的VF
    haveVF=$(lspci | grep "Ethernet controller" | grep "Virtual Function" | wc -l)
    if [ $haveVF -gt 0 ]; then
        write_messages err "==============Exist VF network devs, please remove VF network devs first, we need a Non-VF initial env==================="
        test_fail
    fi

    network_pci_num=$(lspci | grep "Ethernet controller" | wc -l)
    if [ $network_pci_num -lt 1 ]; then
        write_messages err "There is no network dev, exit."
        test_fail
    fi

    network_pci_bdf=$(lspci | grep "Ethernet controller" | awk '{ print $1 }')

    echo "${network_pci_bdf}" | while read network_port_bdf
    do
        let network_pci_count++
        write_messages info "==============Check initial env for network port ${network_pci_count}: "${network_port_bdf}"=================="
        network_port_sriov_check "${network_port_bdf}"
        if [ $? -ne 0 ]; then
            write_messages err "Network port ${bdf} SR-IOV feature check failed."
        fi
    done
    return 0
}

# step 2: create vf dev
function do_network_vf_dev_create()
{
    local phydev_bdf_extend=""
    local max_sriov_vf_num=0

    phydev_bdf_extend=$1
    if [ "${phydev_bdf_extend}" == "" ]; then
        write_messages info "Network phy bdf info is null."
        test_fail
    fi

#   get the max num of sriov_vf of a pci device.
    max_sriov_vf_num=$(cat /sys/bus/pci/devices/"${phydev_bdf_extend}"/sriov_totalvfs)
    if [ $? -ne 0 ]; then
        write_messages err "Get max num of VF failed, pci device: ${phydev_bdf_extend}."
        return 1
    fi
    if [ $max_sriov_vf_num -lt 1 ]; then
        write_messages err "The max num of VF: $max_sriov_vf_num is invalid, pci device: ${phydev_bdf_extend}."
        return 1
    fi
#   start create vf, we create the max num of virtrual function.
    echo $max_sriov_vf_num > /sys/bus/pci/devices/"${phydev_bdf_extend}"/sriov_numvfs
    if [ $? -ne 0 ]; then
        write_messages err "Create VF failed."
        return 1
    fi
    return 0
}

function network_vf_dev_create()
{
    network_pci_bdf_extend=$(echo $PCI_SLOT_NAME)
    network_pci_bdf=$(echo ${network_pci_bdf_extend} | cut -d ':' -f 2-)
    write_messages info "==============Check SR_IOV for: "${network_pci_bdf_extend}"=================="
    network_port_sriov_check "${network_pci_bdf}"
    if [ $? -ne 0 ]; then
        write_messages err "Network port ${network_pci_bdf} SR-IOV is not supported, can not create vf for it."
        continue
    fi
    write_messages info "=============Support SR-IOV, create vf for: "${network_pci_bdf}"=================="
    do_network_vf_dev_create "${network_pci_bdf_extend}"
    if [ $? -ne 0 ]; then
        write_messages err "Create VF devs for Phy dev ${network_pci_bdf} failed."
        clear_env
        test_fail
    fi
    write_messages info "=============Create vf successfully for: "${network_pci_bdf}"=================="
    return 0
}

function network_isvf_check()
{
    local iface=$1
    local pci_bus_bdf_info=""
    local isVF=0

    if [ "${iface}" == "" ];then
        write_messages err "Input arg: iface info is null."
        test_fail
    fi
    pci_bus_bdf_info=$(ethtool -i "${iface}" | grep "bus-info" | awk '{ print $2 }')
    if [ $? -ne 0 ];then
        write_messages err "Get buf-info failed for Iface: "${iface}"."
        clear_env
        test_fail
    fi
    isVF=$(lspci -vvv -s "${pci_bus_bdf_info}" | grep "Virtual Function" | wc -l)
    if [ $isVF -ne 1 ];then
        write_messages info "Iface ${iface}, bdf ${pci_bus_bdf_info} is not virtual function, do not handle."
        return 1
    fi
    write_messages info "Iface ${iface}, bdf ${pci_bus_bdf_info} is virtual function, prepare to configure ip addr."
    return 0
}

# 定义一个函数来检查IP地址是否合法
function netmask_format_check() {
    local ip=$1
    local stat=1

    if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
        OIFS=$IFS
        IFS='.'
        ip=($ip)
        IFS=$OIFS
        if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then
            stat=0
        fi
    fi

    if [ $stat -eq 0 ];then
        write_messages info "Valid address: "${ip}""
    else
        write_messages err "Invalid address: "${ip}""
    fi
    return $stat
}

function try_assign_ip_for_vf()
{
    local input_prefix_ip=$1
    local avail_ip=""
    local ip_fourth_num_start=100
    local ip_fourth_num_cur=$ip_fourth_num_start

    avail_ip="${input_prefix_ip}.${ip_fourth_num_cur}"

    while true
    do
        sleep 3
        write_messages info "ping_check "${avail_ip}"."
        ping_check "${avail_ip}"
        if [ $? -eq 0 ]; then
            write_messages info "ping "${avail_ip}" successfully, the ip is used, continue try another one."
        else
             write_messages info "ping "${avail_ip}" failed, the ip is not uesed, it is avail."
             declare -g network_avail_ip="${avail_ip}"
             break
        fi
        ip_fourth_num_cur=$(($ip_fourth_num_cur + 1))
        avail_ip="${input_prefix_ip}.${ip_fourth_num_cur}"
        if [ $ip_fourth_num_cur -gt 255 ];then
            write_messages info "${avail_ip} is the max-tried ip addr, non-avail ip."
            return 1
        fi
    done
    return 0
}

# 遍历链表并打印节点值
traverseList() {
  local curr=$head

  while [ $curr -ne -1 ]; do
    local currValue=${Node[$curr]}
    echo $currValue

    curr=${Node["$curr.next"]}
  done
}

function create_ifcfg_conf_for_iface()
{
    local Iface_list=""
    local ifcfg_conf_file=""
    local hwaddr=""

    if [ ! -d /etc/sysconfig/network-scripts ]; then
        write_messages err "*************/etc/sysconfig/network-scripts dir is not exist.*************"
        clear_env
        test_fail
    fi
    pushd /etc/sysconfig/network-scripts

    current_time=$(date +%Y%m%d%H%M%S)
    curDir=$(pwd)
    touch ifcfg-${current_time}
    if [ ! -f ${curDir}/ifcfg-${current_time} ]; then
        write_messages err "*************ifcfg file is not exist.*************"
        clear_env
        test_fail
    fi
    cat << EOF >> ${curDir}/ifcfg-${current_time}
DEVICE=${current_time}
TYPE="Ethernet"
NAME==${current_time}
HWADDR=xx:xx:xx:xx:xx:xx
BOOTPROTO=none
ONBOOT=yes
PEERDNS=no
RX_MAX=\`ethtool -g "\$DEVICE" | grep 'Pre-set' -A1 | awk '/RX/{print \$2}'\`
RX_CURRENT=\`ethtool -g "\$DEVICE" | grep "Current" -A1 | awk '/RX/{print \$2}'\`
[[ "\$RX_CURRENT" -lt "\$RX_MAX" ]] && ethtool -G "\$DEVICE" rx "\$RX_MAX"
EOF
    #   遍历所有的VF对应的网络设备并为创建ifcfg配置文件
    Iface_list=$(ifconfig -a | grep -o '^[^[:space:]]\+' | awk -F ':' '{ print $1 }')
    echo "${Iface_list}" | while read iface
    do
        ifcfg_conf_file="ifcfg-${iface}"
        if [ -f ${ifcfg_conf_file} ]; then
            write_messages info "*************${ifcfg_conf_file} is exist, do not need to create.*************"
            continue
        fi
        # 配置文件存入链表
        appendNode ${ifcfg_conf_file}
        # 创建新的ifcfg配置文件
        write_messages info "*************${ifcfg_conf_file} is not exist, create.*************"
        cp  ${curDir}/ifcfg-${current_time} ${ifcfg_conf_file}
        if [ ! -f ${ifcfg_conf_file} ]; then
            write_messages err "*************${ifcfg_conf_file} is created failed.*************"
            clear_env
            test_fail
        fi
        # 修改新创建的ifcfg配置文件中的DEVICE, HWADDR, NAME 三个字段
        sed -i "s/DEVICE=.*/DEVICE=${iface}/" ${ifcfg_conf_file}
        hwaddr=$(ifconfig ${iface} | grep -w ether | awk -F " " '{ print $2}')
        sed -i "s/HWADDR=.*/HWADDR=${hwaddr}/" ${ifcfg_conf_file}
        sed -i "s/NAME=.*/NAME=${iface}/" ${ifcfg_conf_file}
    done
    rm -f  ${curDir}/ifcfg-${current_time}
    popd
}

function restart_network_service()
{
    local flag="false"

    if systemctl list-units -t service| grep "NetworkManager.service" &>/dev/null;then
        if ! systemctl status NetworkManager.service  >/dev/null 2>&1; then
            systemctl restart NetworkManager.service
            systemctl is-active NetworkManager.service && flag="true"
        else
        systemctl is-active NetworkManager.service && flag="true"
        fi
    fi

    if systemctl list-units -t service| grep "network.service" &>/dev/null;then
        if ! systemctl status network.service >/dev/null 2>&1;then
            systemctl restart network.service
            systemctl is-active network.service && flag="true"
        else
            systemctl is-active network.service && flag="true"
        fi
    fi

    if [ $flag == "false" ];then
        write_messages info "Network service is dead. please check."
        clear_env
        test_fail
    fi
}

function server_kill() {
    server_down="kill -9 \`pidof iperf3\` &>/dev/null"
    $essh $server_ip $server_down || return 1
    return 0
}

function server_run() {
    echo "start server listen"
    server_kill
    server_cmd="iperf3 -s"
    $essh $server_ip $server_cmd &
    if [ $? -eq 0 ]; then
        echo "server listen success"
        sleep 3
        return 0
    else
        echo "server listen failed"
        return 1
    fi
}

function client_run() {
    echo "start client connect"
    # i： --interval #，每次报告的间隔，单位为秒
    # -P, --parallel #，测试数据流并发数量；
    # -f, --format [kmgKMG]，报告中所用的数据单位，Kbits, Mbits, KBytes, Mbytes；
    # -B, --bind ，绑定指定的网卡接口；

    client_cmd="iperf3 -t $runtime -i 30 -P 10 -f g -c $server_ip"
    run_cmd "$client_cmd"
    if [ $? -eq 0 ]; then
        echo "client connect success"
        return 0
    else
        echo "client connect failed"
        return 1
    fi
}

function run_throughput_test() {
    echo "run network throughput test!"
    server_ip=$LTS_IPADDRESS
    echo "test lts ip info: $server_ip"
    bash ../../../utils/sshconf.sh setup
    server_run
    if [ $? -ne 0 ]; then
        clear_env
        test_fail
    fi
    client_run
    if [ $? -ne 0 ]; then
        clear_env
        test_fail
    fi
}

function network_throughput_test()
{
    set_server_ip
    run_throughput_test
    server_kill
}

trap "bash ../../../utils/sshconf.sh restore" EXIT

network_port_check
if [ $? -eq 0 ]; then
    write_messages info "*************Step1: Check initial env successfully.*************"
else
    write_messages info "*************Step1: Check initial env failed.*************"
fi

network_vf_dev_create
if [ $? -eq 0 ]; then
    write_messages info "*************Step2: Create VF devs successfully.*************"
else
    write_messages info "*************Step2: Create VF devs failed.*************"
fi

create_ifcfg_conf_for_iface
if [ $? -eq 0 ]; then
    write_messages info "*************Step2: Configure ifcfg conf file for VF devs successfully.*************"
else
    write_messages info "*************Step2: Configure ifcfg conf file for VF devs failed.*************"
fi

restart_network_service

network_throughput_test
if [ $? -eq 0 ]; then
    write_messages info "*************Step3: Iperf throughput test successfully.*************"
else
    write_messages info "*************Step3: Iperf throughput test failed.*************"
fi

clear_env

test_pass

